package utils;

import openfl.Assets;
import haxe.xml.Fast;
import haxe.xml.Parser;
import flixel.FlxSprite;
import flixel.addons.tile.FlxTilemapExt;

class TiledMapLoader
{

	public var width(default, null): Int;			// ancho del mapa (en tiles)
	public var height(default, null): Int;			// alto del mapa (en tiles)
	public var totalWidth(default, null): Int;		// ancho del mapa (en pixels)
	public var totalHeight(default, null): Int;		// alto del mapa (en pixels)
	public var tileWidth(default, null): Int;		// ancho del tile
	public var tileHeight(default, null): Int;		// alto del tile
	
	// variables de ayuda para leer el .xml
	private var xml: Xml;
	private var fastXml: Fast;
	private var mapElement: Fast;

	/**
	 * Crea un nuevo objeto de la clase y prepara los XML para ser leidos
	 * 
	 * IMPORTANTE: 	* Las capas de patrones deben guardarse en modo "CSV" (Comma Separated Values).
	 *				  Ésto puede configurarse en desde Tiled en el menú "Mapas"->"Atributos del mapa"->"Formato de capa".
	 * 			  	* El primer tile del tileset (el que tiene nro de índice 0) se toma como tile vacío.
	 * 
	 * @param	Una cadena representando los datos XML o el nombre del archivo de Tiled.
	 */
	public function new(LevelData: Dynamic)
	{
		var str:String = "";	// la cadena con los datos XML
		if (Std.is(LevelData, Class)) // si el parámetro es una cadena
		{
			str = Type.createInstance(LevelData, []);
		}
		else if (Std.is(LevelData, String))  // si el parámetro es el nombre de un archivo
		{
			str = Assets.getText(LevelData);
			if(str == null) throw "TiledMapLoader::new(): No asset named: \""+str+"\"";
		}
		xml = Parser.parse(str);
		fastXml = new Fast(xml);
		mapElement = fastXml.node.map;
		width = Std.parseInt(mapElement.att.width);
		height = Std.parseInt(mapElement.att.height);
		tileWidth = Std.parseInt(mapElement.att.tilewidth);
		tileHeight = Std.parseInt(mapElement.att.tileheight);
		totalWidth = width * tileWidth;
		totalHeight = height * tileHeight;
	}


	/**
	 * Carga una capa de patrones de un archivo de Tiled y devuelve un objeto FlxTilemap. 
	 *	
	 * IMPORTANTE: 	* Las capas de patrones deben guardarse en modo "CSV" (Comma Separated Values).
	 *				  Ésto puede configurarse en desde Tiled en el menú "Mapas"->"Atributos del mapa"->"Formato de capa".
	 * 			  	* Al utilizar el método collide() con un objeto de tipo FlxTilemap siempre collisionar el mapa
	 *				  contra los objetos y no al revés. Así se evitan errores extraños en la colisión.
	 *				  ej: FlxG.collide(map, obj, notifyCallback);
	 * 
	 * @param	Una cadena con el nombre de la capa de patrones que deseamos cargar.
	 * @return	Un objeto de tipo FlxTilemap correspondiente a la capa de patrones especificada.
	 */ 
	public function loadPatternLayer(LayerName: String, TileGraphic: Dynamic): FlxTilemapExt
	{
		for ( layer in mapElement.nodes.layer ) {
			if (layer.has.name && layer.att.name == LayerName){
				var layerData = layer.node.data.innerData;
				var TileMap: FlxTilemapExt = new FlxTilemapExt();
				TileMap.loadMap(layerData, TileGraphic, tileWidth, tileHeight, 0, 1);
				return TileMap;
			}
		}
		throw "ERROR: No se encontró la capa de patrones \"" + LayerName + "\"";
	}



	public function loadObjectGroup(ObjectGroupName: String, ObjectLoadCallback: TiledObject -> Void): Void
	{
		// recorre los grupos de objetos
		for (objectgroup in mapElement.nodes.objectgroup)
		{
			// si encuentra un grupo de objetos con el nombre especificado
			if (objectgroup.has.name && objectgroup.att.name == ObjectGroupName){
				// recorre los objetos dentro del grupo e invoca a la función
				// callback con cada uno de ellos
				for (obj in objectgroup.nodes.object){
					var tiledObject = new TiledObject(obj);
					ObjectLoadCallback(tiledObject);
				}
				return;		
			}
		}
	}
}

class TiledObject
{
	public var name(default, null): String;
	public var type(default, null): String;
	public var x(default, null): Float;
	public var y(default, null): Float;
	public var width(default, null): Float;
	public var height(default, null): Float;
	public var properties(default, null): TiledObjectProperties;

	public function new(Obj: Fast)
	{
		name = Obj.has.name ? Obj.att.name : null;
		type = Obj.has.type ? Obj.att.type : null;
		x = Std.parseFloat(Obj.att.x);
		y = Std.parseFloat(Obj.att.y);
		width = Std.parseFloat(Obj.att.width);
		height = Std.parseFloat(Obj.att.height);
		properties = new TiledObjectProperties(Obj);
	}

	public function toFlxSprite(): FlxSprite
	{
		return new FlxSprite(x, y);
	}
}


class TiledObjectProperties
{
	public function new(Obj: Fast)
	{
		_obj = Obj;
		_properties = new Map<String, String>();
		if(Obj.hasNode.properties)
		{
			for (property in Obj.node.properties.nodes.property)
			{
				_properties.set(property.att.name, property.att.value);
			}
		}
	}

	public function getInt(PropertyName: String): Int
	{
		return Std.parseInt(getString(PropertyName));
	}

	public function getFloat(PropertyName: String): Float
	{
		return Std.parseFloat(getString(PropertyName));
	}

	public function getString(PropertyName: String): String
	{
		var property = _properties.get(PropertyName);
		if(property == null){
			throw "El objeto de tipo \""+(_obj.has.type ? _obj.att.type : "[null]")+"\" y nombre \""+ (_obj.has.name ? _obj.att.name : "[null]")+"\" no posee la propiedad \""+PropertyName+"\"";
		}
		return property;
	}

	public function getStringDefault(PropertyName: String, DefaultValue: String): String
	{
		var property = _properties.get(PropertyName);
		if(property == null){
			return DefaultValue;
		}
		return property;
	}

	public function getFloatDefault(PropertyName: String, DefaultValue: Float): Float
	{
		return Std.parseFloat(getStringDefault(PropertyName, Std.string(DefaultValue)));
	}

	public function getIntDefault(PropertyName: String, DefaultValue: Int): Int
	{
		return Std.parseInt(getStringDefault(PropertyName, Std.string(DefaultValue)));
	}

	private var _properties: Map<String, String>;
	private var _obj: Fast;
}
